//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include <stdio.h>
#include <stdlib.h>
#include "_sprintf.h"

#include "ctrl.h"
#include "console.h"

#ifdef _DEBUG
#define Assert(x)   if (!((bool)(x))) { \
                        DebugBreak(); \
                    }
#else
#define Assert(x)
#endif

EXTERN IDeviceDriver * CDECL CreateConsoleDev();

IDeviceDriver  *g_pConsole = NULL;

//#if !defined(_RELEASE) || defined(_PRERELEASE)
static IAbsoluteIO *s_pLogFileIO = NULL;
static IBasicFile *s_pLogFile = NULL;
static UInt32 s_uOffset = 0;
static bool s_bLog = FALSE;
static bool s_bTime = TRUE;
static WStringBuf_<32> s_pLogDirPath;
static DzMutex s_mutex;

ELAPI _CProfile_GetWString(WString appName, WString keyName,
        WStringBuf *pReturnedString);

EXTERN void CDECL WriteToConsole(const char *szMessage);
EXTERN bool_t CDECL DzIsInIsr();

static void ConvertEzTimeToDateTime(
    /* [in] */ SystemTime ezTime,
    /* [out] */ struct DateTime * pDateTime)
{
    if (!pDateTime) {
        return;
    }

#define IS_LEAP_YEAR(x)   ((x)% 400 == 0 || (!((x) & 3) && (x) % 100 != 0))

    UCHAR s_DaysInMonth1[] = {0,31,29,31,30,31,30,31,31,30,31,30,31};
    UCHAR s_DaysInMonth0[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
    pDateTime->milliseconds = (WORD)(ezTime.microseconds/1000);
    ULONG tm = (ULONG)(ezTime.seconds);//unit is second
    pDateTime->second    = (WORD) (tm % 60);
    tm /= 60;
    pDateTime->minute    = (WORD) (tm % 60);
    tm /= 60;
    pDateTime->hour      = (WORD) (tm % 24);
    tm /= 24;   // tm becomes day
    pDateTime->dayofweek = (WORD) ((tm + 4) % 7);        // it's Thursday on 1/1/1970

    // count from 1970. year 2100 will be count as a leap year falsely
    // due to simplify of IS_LEAP_YEAR algorithm.
    //
    UInt32 year = 70;
    while (1) {
        if (IS_LEAP_YEAR(year + 300)) {
            if (tm < 366) break; else tm -= 366;
        }
        else {
            if (tm < 365) break; else tm -= 365;
        }
        year++;
    }
    UInt32 month = 1;
    UCHAR *  DaysInMonth = IS_LEAP_YEAR(year + 300) ? s_DaysInMonth1 : s_DaysInMonth0;
    while (1) {
        if (tm < DaysInMonth[month])
            break;
        else
            tm -= DaysInMonth[month];
        month++;
    }
    pDateTime->year  = year + 1900;
    pDateTime->month = month;
    pDateTime->day   = (short)++tm;
    return;
}

inline char *GetCurTimeFmt(char *buf, char *fmt)
{
    SystemTime ezTime;
    DzGetSystemTime(&ezTime);
    ezTime.seconds += 28800;  // Add 8 hours for time zone
    struct DateTime dateTime;
    ConvertEzTimeToDateTime(ezTime, &dateTime);
    sprintf(buf, fmt, dateTime.year, dateTime.month, dateTime.day,
                    dateTime.hour, dateTime.minute, dateTime.second);
    return buf;
}

static void InitLogFile()
{
    IDiskManager *pDiskMgr = NULL;
    IBasicDirectory* pDir = NULL;
    IBasicFile *pFile = NULL;
    WStringBuf_<32> wsbName;

    _print = &BspWriteConsole;
    ECode ec = DzFindService(L"diskmgr", (PInterface *)&pDiskMgr);
    if (FAILED(ec)) {
        _print = &WriteToConsole;
        return;
    }

    ec = pDiskMgr->AcquireDirInterface(s_pLogDirPath, &pDir, &wsbName);
    pDiskMgr->Release();
    if (FAILED(ec)) goto Exit;

    if (wsbName.GetLength()) {
        ec = pDir->CreateDirectory(wsbName, FileAccessMode_ReadWrite);
        if (FAILED(ec) && (ec != E_FS_FILE_EXISTS)) {
            goto Exit;
        }
        ec = pDir->Lookup(wsbName, &pFile);
        if (FAILED(ec)) {
            goto Exit;
        }
        pDir->Release();
        pDir = NULL;
        pDir = (IBasicDirectory *)pFile->Probe(EIID_IBasicDirectory);
        if (pDir != NULL) {
            pDir->AddRef();
        }
        pFile->Release();

        if (pDir == NULL) goto Exit;
    }

    char szLogFile[32];
    GetCurTimeFmt(szLogFile, "%d%02d%02d_%02d%02d%02d.log");
    _strtowcs(s_pLogDirPath, szLogFile);
    ec = pDir->CreateFile(s_pLogDirPath, FileAccessMode_ReadWrite, FALSE, &s_pLogFile);
    if (FAILED(ec)) {
        goto Exit;
    }

    s_pLogFileIO = (IAbsoluteIO*)s_pLogFile->Probe(EIID_IAbsoluteIO);
    if (s_pLogFileIO == NULL) {
        s_pLogFile->Release();
        s_pLogFile = NULL;
        goto Exit;
    }
    s_pLogFileIO->AddRef();
    if (pDir) pDir->Release();
    _print = &WriteToConsole;
    return;
Exit:
    s_bLog = FALSE;
    if (pDir) pDir->Release();
    _print = &WriteToConsole;
    return;
}
//#endif // !_RELEASE || _PRERELEASE

EXTERN void CDECL WriteToConsole(const char *szMessage)
{
#ifndef ELASTOS_RC    
//#if !defined(_RELEASE) || defined(_PRERELEASE) || defined(_TEST_TYPE)
    ECode ec;
    uint32_t uLength = strlen(szMessage);

    if (0 == uLength) {
        return;
    }

    Assert(NULL != g_pConsole);

    MemoryBuf ezbbbContent((Byte*)szMessage, uLength);
    ec = g_pConsole->Write((uint32_t )-1,
                                ezbbbContent, NULL, NULL);

    Assert(NOERROR == ec);

//#if !defined(_RELEASE) || defined(_PRERELEASE)
    if (s_bLog && !DzIsInIsr()) {
        WaitResult wr;
        s_mutex.Lock(&wr);
        if (s_pLogFileIO) {
            Int32 uWrited = 0;
            _print = &BspWriteConsole;

            if (s_bTime) {
                //Add time flag
                char szTime[32];
                GetCurTimeFmt(szTime, "[%d-%02d-%02d %02d:%02d:%02d] ");
                MemoryBuf ebbTime((Byte *)szTime, 22);
                ec = s_pLogFileIO->Write(s_uOffset, ebbTime, &uWrited);
                s_uOffset += uWrited;
                s_bTime = FALSE;
            }

            ec = s_pLogFileIO->Write(s_uOffset, ezbbbContent, &uWrited);
            s_uOffset += uWrited;

            if (szMessage[uLength -1] == '\n') {
                s_bTime = TRUE;
            }

            if (SUCCEEDED(ec)) s_pLogFile->Sync();
            _print = &WriteToConsole;
            s_mutex.Unlock();
        }
        else {
            InitLogFile();
            s_mutex.Unlock();
        }
    }
//#endif // !_RELEASE || _PRERELEASE
//#endif // !_RELEASE || _PRERELEASE || _TEST_TYPE
#endif //ELASTOS_RC    
}

//
// The console driver's classfactory
//
EXTERN IDeviceDriver * CDECL CreateConsole(uint_t uDeviceNo, void *pvParameter)
{
    ECode ec;
    g_pConsole = new Console;
    if (NULL == g_pConsole) {
        Assert(0 && "There is not enough memory");
        return NULL;
    }

    // Initialize keyboard hardware
    ec = ((Console*)g_pConsole)->Initialize();
    if (FAILED(ec)) {
        delete g_pConsole;
        g_pConsole = NULL;
        return NULL;
    }

    _print = &WriteToConsole;

    g_pConsole->AddRef();

//#if !defined(_RELEASE) || defined(_PRERELEASE)
    ec = _CProfile_GetWString(L"SystemLog", L"LogDirPath",
                &s_pLogDirPath);
    if (SUCCEEDED(ec)) {
        s_bLog = TRUE;
    }
//#endif // !_RELEASE || _PRERELEASE

    return g_pConsole;
}

ECode Console::Initialize()
{
    m_pConsoleDevDriver = CreateConsoleDev();

    if (NULL == m_pConsoleDevDriver) {
        return E_FAIL;
    }

    for (int i = 0; i < HISTORY_NUMBER; i++) {
        m_ppHistory[i] = (char*)malloc(sizeof(char) * HISTORY_MAX_LENGTH);
        if (NULL == m_ppHistory[i]) {
            return E_OUT_OF_MEMORY;
        }
        *m_ppHistory[i] = '\0';
    }

    m_bBuffer = TRUE;
    m_TempValue = 0;
    m_FirstRecord = 0;
    m_pOldPrint = _print;

    return NOERROR;
}

int Console::TranslateAscii(int ch)
{

    switch (ch) {
        case ASCII_LeftArrow:
            m_TempValue = 0x4B;
            return 0xE0;
        case ASCII_RightArrow:
            m_TempValue = 0x4D;
            return 0xE0;
        case ASCII_Insert:
            m_TempValue = 0x52;
            return 0xE0;
        case ASCII_Delete:
            m_TempValue = 0x53;
            return 0xE0;
        case ASCII_Home:
            m_TempValue = 0x47;
            return 0xE0;
        case ASCII_End:
            m_TempValue = 0x4F;
            return 0xE0;
        case ASCII_UpArrow:
            m_TempValue = 0x48;
            return 0xE0;
        case ASCII_DownArrow:
            m_TempValue = 0x50;
            return 0xE0;
        case ASCII_PageUp:
            m_TempValue = 0x49;
            return 0xE0;
        case ASCII_PageDown:
            m_TempValue = 0x51;
            return 0xE0;

        case ASCII_F1:
            m_TempValue = 0x3B;
            return 0x0;
        case ASCII_F2:
            m_TempValue = 0x3C;
            return 0x0;
        case ASCII_F3:
            m_TempValue = 0x3D;
            return 0x0;
        case ASCII_F4:
            m_TempValue = 0x3E;
            return 0x0;
        case ASCII_F5:
            m_TempValue = 0x3F;
            return 0x0;
        case ASCII_F6:
            m_TempValue = 0x40;
            return 0x0;
        case ASCII_F7:
            m_TempValue = 0x41;
            return 0x0;
        case ASCII_F8:
            m_TempValue = 0x42;
            return 0x0;
        case ASCII_F9:
            m_TempValue = 0x43;
            return 0x0;
        case ASCII_F10:
            m_TempValue = 0x44;
            return 0x0;
        case ASCII_F11:
            m_TempValue = 0x85;
            return 0xE0;
        case ASCII_F12:
            m_TempValue = 0x86;
            return 0xE0;

            //case ASCII_At:
            //m_TempValue = 0x3;
            //return 0x0;

        default:
            break;
    }

    return ch;
}

//move the cursor forward with count steps
void Console::MoveCursorForward(UInt32 count)
{
    ECode ec;
    MemoryBuf ebbMove((Byte*)&count, sizeof(UInt32));

    ec = m_pConsoleDevDriver->Control(7,
                    ebbMove, NULL, NULL);
    assert(NOERROR == ec);
}

//change the cursor shape
void Console::SetCursorType(int cursortype)
{
    ECode ec;
    MemoryBuf ebbBuf((Byte*)&cursortype, sizeof(int));
    ec = m_pConsoleDevDriver->Control(3,
                  ebbBuf, NULL, NULL);
    assert(ec == NOERROR);
}

ECode Console::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }
    assert(bytesToRead > 0);

    if (!m_bBuffer) { //read one char or control key without line buffer
        int c = 0;
        if (m_TempValue) {
            c = m_TempValue;
            m_TempValue = 0;
        }
        else {
            MemoryBuf_<1> ezbbBuf;
            m_pConsoleDevDriver->Read(u64Offset,
                            1, &ezbbBuf, ppCompletionEvent);
            c = TranslateAscii(*(uint8_t *)ezbbBuf.GetPayload());
        }
        *(int*)pBuffer->GetPayload() = c;
        pBuffer->SetUsed(1);
        m_bBuffer = TRUE; //resume line buffer
    }
    else { //read chars with line buffer
        uint8_t ch = 0;
        UInt32 count = 0;
        UInt32 current = 0;
        UInt32 charcount = 0;
        Boolean b_Insert = TRUE;
        MemoryBuf ezbbBuf((Byte*)&ch, 1);

        char* str = (char*)pBuffer->GetPayload();
        memset(str, 0, 1);
        m_TempValue = 0;
        m_CurrentRecord = m_FirstRecord;

        do {
            //read one char from console device
            m_pConsoleDevDriver->Read(u64Offset,
                        1, &ezbbBuf, ppCompletionEvent);

            switch (ch) {
                case ASCII_F1:
                case ASCII_F2:
                case ASCII_F3:
                case ASCII_F4:
                case ASCII_F5:
                case ASCII_F6:
                case ASCII_F7:
                case ASCII_F8:
                case ASCII_F9:
                case ASCII_F10:
                case ASCII_F11:
                case ASCII_F12:
                    // these keys value is larger than 0x80
                    // two these keys together with become a chinese charactor
                    // so we capture them here & don't let them trans to
                    // default case
                    break;
                case ASCII_BackSpace:
                    if (current && charcount) {
                        if (current == charcount) { //when cursor is at the end
                            str[current - 1] = 0;
                            MoveCursorForward(1);
                            _print(" ");
                            MoveCursorForward(1);
                        }
                        else {
                            memmove(str + current - 1, str + current,
                                                       charcount - current + 1);
                            MoveCursorForward(1);
                            _print(str + current - 1);
                            _print(" ");
                            MoveCursorForward(charcount - current + 1);
                        }
                        charcount--;
                        current--;
                    }
                    break;

                case ASCII_Home:       // goto beginning of string

                    MoveCursorForward(current);
                    current = 0;

                    break;

                case ASCII_Delete:       // delete character under cursor
                    if (current != charcount && charcount > 0) {
                        memmove(str + current, str + current + 1,
                                                          charcount - current);
                        charcount--;
                        _print(str + current);
                        _print(" ");
                        MoveCursorForward(charcount - current + 1);
                    }

                    break;

                case ASCII_End:         //goto end of string

                    if (current != charcount) {
                        _print(str + current);
                        current = charcount;
                    }

                    break;

                case KEY_ESC:

                    if (charcount) { //erase the existing command
                        MoveCursorForward(current); //Move cursor to the head

                        for (count = 0; count < charcount; count++) {
                            str[count] = ' ';
                        }
                        str[charcount] = '\0';
                        _print(str); //use blank to cover existing char

                        MoveCursorForward(charcount); //move cursor to the head

                        current = 0;
                        charcount = 0;
                        str[0] = '\0';
                    }

                    break;

                case ASCII_LeftArrow:            // move cursor left

                    if (current > 0) {
                        MoveCursorForward(1);
                        current--;
                    }

                    break;

                case ASCII_RightArrow:            //move cursor right

                    if (current != charcount) {
                        char c[2];
                        c[0] = str[current];
                        c[1] = '\0';
                        _print(c); //just print char int the cursor
                        current++;
                    }

                    break;

                case ASCII_UpArrow:       //get previous command from buffer

                    if (charcount) {
                        MoveCursorForward(current); //Move cursor to the head

                        for (count = 0; count < charcount; count++) {
                            str[count] = ' ';
                        }
                        str[charcount] = '\0';
                        _print(str); //use blank to cover existing char

                        MoveCursorForward(charcount); //move cursor to the head
                    }

                    //put the previous command into the buffer, if exist
                    if ('\0' != m_ppHistory[(HISTORY_NUMBER + m_CurrentRecord
                        - 1) % HISTORY_NUMBER][0]
                        && ((HISTORY_NUMBER + m_CurrentRecord - 1) %
                        HISTORY_NUMBER) != m_FirstRecord) {
                        if ((m_CurrentRecord == m_FirstRecord) &&
                            (0 == m_TempValue)) {
                            m_TempValue = 1;
                        }
                        else {
                            m_CurrentRecord = (HISTORY_NUMBER + m_CurrentRecord
                                               - 1) % HISTORY_NUMBER;
                        }
                    }
                    strcpy(str, m_ppHistory[m_CurrentRecord]);

                    charcount = strlen(str);
                    current = charcount;
                    if (str[0]) _print(str);

                    break;

                case ASCII_DownArrow:     //get next command from buffer

                    //put the next command into the buffer, if exist
                    if ('\0' == *m_ppHistory[m_FirstRecord]
                        || m_CurrentRecord == m_FirstRecord) {
                        continue;
                    }
                    else {
                        if (charcount) { //erase the existing command
                            // Move cursor to the head
                            MoveCursorForward(current);

                            for (count = 0; count < charcount; count++) {
                                str[count] = ' ';
                            }
                            str[charcount] = '\0';
                            _print(str); //use blank to cover existing char

                            // move cursor to the head
                            MoveCursorForward(charcount);
                        }
                        m_CurrentRecord =
                            (m_CurrentRecord + 1) % HISTORY_NUMBER;
                    }
                    strcpy(str, m_ppHistory[m_CurrentRecord]);
                    charcount = strlen(str);
                    current = charcount;

                    if (str[0]) _print(str);

                    break;

                case KEY_CTL_C:

                    str[0] = '\n';
                    charcount = 1;
                    _print("\n"); //change line without erasing command

                    break;

                case ASCII_Insert: //when insert, change cursor shape

                    if (b_Insert) {
                        SetCursorType(SOLIDCURSOR);
                        b_Insert = FALSE;
                    }
                    else {
                        SetCursorType(NORMALCURSOR);
                        b_Insert = TRUE;
                    }

                    break;

                case ASCII_ENTER:            //end input

                    if (str[0]) {
                        if (charcount < (HISTORY_MAX_LENGTH - 1)) {
                            str[charcount] = '\0';
                        }
                        else {
                            str[HISTORY_MAX_LENGTH - 1] = '\0';
                        }

                        if (strcmp(str, m_ppHistory[m_FirstRecord])) {
                            m_FirstRecord =
                                (m_FirstRecord + 1) % HISTORY_NUMBER;
                            strcpy(m_ppHistory[m_FirstRecord], str);
                        }
                    }

                    if (charcount < (UInt32)bytesToRead) {
                        str[charcount++] = '\n';
                    }

                    _print("\n");

                    m_TempValue = 0;

                    break;

                default:          //insert character into string...

                    if ((ch >= 32 && ch <= 128) && (charcount <
                        (UInt32)bytesToRead)) {
                        if (b_Insert && current != charcount) {
                            str[charcount + 1] = '\0';
                            for (count = charcount; count > current; count--)
                                str[count] = str[count - 1];
                            str[current++] = ch;
                            _print(str + current - 1);
                            MoveCursorForward(charcount - current + 1);
                            charcount++;
                        }
                        else {
                            if (current == charcount) {
                                charcount++;
                                str[charcount] = '\0';
                            }
                            char c[2];
                            str[current++] = ch;
                            c[0] = ch;
                            c[1]= '\0';
                            _print(c);
                        }
                    }

                    break;
            }
        } while (ch != ASCII_ENTER && ch != KEY_CTL_C &&
            charcount < (UInt32)bytesToRead);
        SetCursorType(NORMALCURSOR); //set the cursor shape normal
        pBuffer->SetUsed(charcount);
    }

    return NOERROR;
}

ECode Console::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return m_pConsoleDevDriver->Write(u64Offset,
                        buffer,
                        pBytesWritten,
                        ppCompletionEvent);
}

ECode Console::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (CTRL_SETLINEBUF == nControlCode) { //cancel the default line buffer
        m_bBuffer = FALSE;
        return NOERROR;
    }
    else {
        return m_pConsoleDevDriver->Control(nControlCode,
                        inBuffer,
                        pOutBuffer,
                        ppCompletionEvent);
    }
}
